<?php

namespace App\Console\Commands\Fix;

use App\Common\Libs\Ali\Oss;
use App\Common\Libs\ArrayHelper;
use App\Common\Libs\Dispatch\Console;
use App\Common\Libs\HttpClient;
use App\Common\Libs\Json;
use App\Console\BaseCommand;
use App\Console\Commands\Controller\UploadCommand;
use App\Consts\GlobalArticleConst;
use App\Consts\GlobalConsoleConst;
use App\Consts\GlobalConst;
use App\Consts\GlobalDynamicConst;
use App\Consts\GlobalSpiderConst;
use App\Consts\GlobalUploadConst;
use App\Consts\GlobalUserConst;
use App\Models\LogReplaceResources;
use App\Models\SpiderContent;
use App\Models\SpiderInfo;
use App\Models\SysArticle;
use App\Models\SysArticleContent;
use App\Models\SysArticleRepairUrl;
use App\Models\User;
use Illuminate\Database\Query\Builder;

/**
 * 修复文章内容脚本
 * Class OssArticleFixCommand
 * @package App\Console\Commands\Fix
 */
class ArticleFixCommand extends BaseCommand
{

    /**
     * The name and signature of the console command.
     * [!] 参数说明：
     *      --ids       :  要处理的ID数组，逗号分割
     *      --lastId    :  从指定ID开始处理
     *      --cacheDays :  处理指定缓存天数内的数据
     *      --isForce   :  是否强制更新
     *
     * @var string
     */
    protected $signature = 'fix:article {action} {--ids=} {--lastId=} {--cacheDays=}  {--isForce=}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'fix article oss file';

    /**
     * 文章修复 - 文章内容修复
     * [!]  修复步骤如下：
     *      - 获取 oss 爬取结果，如果没有需要重新爬取一遍原文文章内容，并上传到 oss；
     *      - 将爬取结果和表里的记录对比（忽略图片替换），如果不同需要更新表记录，
     *        更新表记录需要对爬取结果进行图片替换，同时下载图片并上传到 oss；
     *      - 将替换后的爬取结果与原 oss 结果对比，如果不同需要重新上传到 oss，
     *        重新上传到 oss，只需要根据表记录进行更新即可。
     * @return int
     */
    public function content()
    {
        // 接收参数
        $isForce = $this->_getIsForce();
        $subMode = $this->_getSubModel();
        $finish = $subMode->count();
        // 每 100 单位合并处理一次
        $subMode->chunkById(100, function ($list) use ($finish, $isForce) {
            foreach ($list as $item) {
                static $index = 1;
                $id = $item->article_id;
                $userId = $item->author_id;
                $url = $item->original_url;
                // 原文链接为空，则跳过
                if (empty($url)) {
                    $info = "[$index/$finish] original_url is empty : $id - $url";
                    $this->warn($info);
                    $this->logger($info, false, 'script');
                    continue;
                }
                // 如果是全部执行，每 500 篇暂停 3 秒
                if ($index%500 == 0) sleep(3);
                $spider = SpiderInfo::getModel()->where('url', $url)->first();
                $article = SysArticle::getModel($id)->where('article_id', $id)->first();
                if (!empty($spider)) {
                    // 原文链接已失效，跳过
                    if ($spider->is_invalid == GlobalSpiderConst::INVALID_ENS) {
                        $info = "[$index/$finish] original_url is invalid : $id - $url";
                        $this->warn($info);
                        $this->logger($info, false, 'script');
                        continue;
                    }
                    // 0.1s 执行一次
                    usleep(100000);
                    // 获取爬取信息
                    $ossSpider = $this->_getOssSpider($article);
                    $ossSpiderContent = $ossSpider['content'] ?? '';
                    if (!empty($ossSpiderContent)) {
                        $res = true;
                        // 对比抓取的文章内容和表里的文章内容（忽略图片资源）
                        $articleContent = $article->content;
                        if ($isForce || $this->_getNoImageContent($ossSpiderContent) != $this->_getNoImageContent($articleContent)) {
                            // 更新文章内容
                            $content = $ossSpiderContent;
                            $ossUrl = getConfigByKey('ossUrl');
                            $imgList = $this->_getImageList($content);
                            foreach ($imgList as $key => $img) {
                                $newImg = Oss::getInstance(
                                    GlobalUploadConst::OBJECT_ARTICLE_EDITOR_PATH,
                                    GlobalUploadConst::TYPE_ARTICLE,
                                    GlobalUploadConst::FROM_FRONTEND,
                                    $id
                                )->upload($img);
                                if ($newImg) {
                                    $content = str_replace($img, $ossUrl . '/' . GlobalUploadConst::OBJECT_ARTICLE_EDITOR_PATH . $newImg, $content);
                                }
                                // 保存替换记录
                                $this->saveRepair($id, $userId, $newImg, $img);
                                // 把旧内容，替换图片后的内容存入[spider_content]
                                $this->_saveSpiderContent($url, $article->content, $content);
                                // 修改[sys_article_content_]内容
                                $this->_saveArticleContent($id, $content);
                            }
                            // 同步新的文章内容到 oss
                            $res = $this->_toOss($article);
                            // log info
                            $res = $res ? 'SUCCESS' : 'FAILED';
                            $info = "[$index/$finish] sync oss [article/info] [$res] -  $id .";
                            $this->logger($info, true, 'script');
                        }
                        // log info
                        $res = $res ? 'SUCCESS' : 'FAILED';
                        $info = "[$index/$finish] update `sys_article` content [$res] -  $id .";
                        $this->logger($info, true, 'script');
                    } else {
                        $this->info("[$index/$finish] nothing to update `sys_article` : $id .");
                    }
                } else {
                    $info = "[$index/$finish] spider info not exist [spider_info] : $id - $url .";
                    $this->warn($info);
                    $this->logger($info, false, 'script');
                }
                $index++;
            }
        }, 'article_id');
        return 1;
    }

    /**
     * 文章修复 - 封面图片
     * [!] 此处会重新爬取，然后更新 sys_article 表的 cover_url 字段 ，再上传到 oss
     * @return int
     */
    public function cover()
    {
        $subMode = $this->_getSubModel();
        $finish = $subMode->count();
        // 每 100 单位合并处理一次
        $subMode->chunkById(100, function ($list) use ($finish) {
            foreach ($list as $item) {
                static $index = 1;
                $id = $item->article_id;
                $url = $item->original_url;
                $article = SysArticle::getModel($id)->where('article_id', $id)->first();
                if (!empty($article)) {
                    if (empty($article->cover_url)) {
                        // 重新爬取
                        $res = $this->_getOssSpider($article);
                        if (empty($res['cover_url'])) {
                            $info = "[$index/$finish] spider catch failed  : $id - $url .";
                            $this->error($info);
                            continue;
                        }
                        $url = $res['cover_url'];
                        $newImg = Oss::getInstance(
                            GlobalUploadConst::OBJECT_ARTICLE_COVER_PATH,
                            GlobalUploadConst::TYPE_ARTICLE,
                            GlobalUploadConst::FROM_FRONTEND,
                            $id
                        )->upload($url);
                        if ($newImg) {
                            $res = $article->update(['cover_url' => $newImg]);
                        } else {
                            $newImg = 'null';
                            $info = "[$index/$finish] catch failed [article/info] : $id .";
                            $this->warn($info);
                            $res = false;
                        }
                        // log info
                        $res = $res ? 'SUCCESS' : 'FAILED';
                        $tableName = SysArticle::getTableNameById($id);
                        $info = "[$index/$finish] update `{$tableName}` [$res] $id [".env('APP_ENV')."] : "
                            ." cover_url - $url  -->  $newImg";
                        $this->logger($info, true, 'script');
                    } else {
                        $this->info("[$index/$finish] nothing to update [article/info] : $id .");
                    }
                } else {
                    $info = "[$index/$finish] article not exist [sys_article] : $id .";
                    $this->warn($info);
                }
                $index++;
            }
        }, 'article_id');
        return 1;
    }

    /**
     * 文章修复 - 推荐作者
     * [!] 此处会重新爬取，然后更新 spider_info 表的 author_name 字段
     * @return int
     */
    public function author()
    {
        $subMode = $this->_getSubModel();
        $finish = $subMode->count();
        // 每 100 单位合并处理一次
        $subMode->chunkById(100, function ($list) use ($finish) {
            foreach ($list as $item) {
                static $index = 1;
                $id = $item->article_id;
                $url = $item->original_url;
                $article = SysArticle::getModel($id)->where('article_id', $id)->first();
                $spider = SpiderInfo::getModel()->where('url', $url)->first();
                if (!empty($spider)) {
                    if (empty($spider->author_name)) {
                        // 重新爬取
                        $res = $this->_getOssSpider($article);
                        if (empty($res['author_name'])) {
                            $info = "[$index/$finish] spider catch failed  : $id - $url .";
                            $this->error($info);
                            continue;
                        }
                        $name = $res['author_name'];
                        $res = $spider->update(['author_name' => $name]);
                        // log info
                        $res = $res ? 'SUCCESS' : 'FAILED';
                        $info = "[$index/$finish] update `spider_info` [$res] $id [".env('APP_ENV')."] : "
                            ." author_name - $name";
                        $this->logger($info, true, 'script');
                    } else {
                        $this->info("[$index/$finish] nothing to update [article/info] : $id .");
                    }
                } else {
                    $info = "[$index/$finish] spider info not exist [spider_info] : $id - $url .";
                    $this->warn($info);
                }
                $index++;
            }
        }, 'article_id');
        return 1;
    }

    /**
     * 文章修复 - 原文章发布账号名修复
     * [!] 此处会重新爬取，并更新 spider_info 表 account_name 和 is_invalid 字段
     * @return int
     */
    public function accountName()
    {
        $subMode = $this->_getSubModel();
        $finish = $subMode->count();
        // 每 100 单位合并处理一次
        $subMode->chunkById(100, function ($list) use ($finish) {
            foreach ($list as $item) {
                static $index = 1;
                $id = $item->article_id;
                $url = $item->original_url;
                // 原文链接为空，或者来源不是微信，则跳过
                if (empty($url) || $item->spider_original_id != 1) {
                    continue;
                }
                $article = SysArticle::getModel($id)->where('article_id', $id)->first();
                $spider = SpiderInfo::getModel()->where('url', $url)->first();
                if (!empty($spider)) {
                    // 原文链接已失效，跳过
                    if ($spider->is_invalid == GlobalSpiderConst::INVALID_ENS) {
                        $info = "[$index/$finish] original_url is invalid  : $id - $url";
                        $this->warn($info);
                        $this->logger($info, false, 'script');
                        continue;
                    }
                    // 0.1s 执行一次
                    usleep(100000);
                    if (empty($spider->account_name)) {
                        // 重新爬取
                        $res = $this->_getOssSpider($article);
                        if (empty($res['account_name'])) {
                            $info = "[$index/$finish] spider catch failed  : $id - $url - " . json_encode($res);
                            $this->error($info);
                            $this->logger($info, false, 'script');
                            // 标记文章已失效
                            $spider->update(['is_invalid' => GlobalSpiderConst::INVALID_ENS]);
                            continue;
                        }
                        $name = $res['account_name'];
                        $res = $spider->update(['account_name' => $name]);
                        // log info
                        $res = $res ? 'SUCCESS' : 'FAILED';
                        $info = "[$index/$finish] update `spider_info` [$res] $id [".env('APP_ENV')."] : "
                            ." account_name - $name";
                        $this->logger($info, true, 'script');
                    } else {
                        $this->info("[$index/$finish] nothing to update `spider_info` : $id .");
                    }
                } else {
                    $info = "[$index/$finish] spider info not exist [spider_info] : $id - $url .";
                    $this->warn($info);
                    $this->logger($info, false, 'script');
                }
                $index++;
            }
        }, 'article_id');
        return 1;
    }

    /**
     * 文章修复 - 文章未替换的文章内容图片修复
     * [!] 根据 log_replace_resources 表的 is_replaced 字段去修复
     * [!] 此方法，后面可能会被废弃掉（可被 content 方法替换掉）
     * @return int
     */
    public function replace()
    {
        $ids = $this->_getReplaceIds();
        $subMode = $this->_getSubModel($ids);
        $finish = $subMode->count();
        // 每 100 单位合并处理一次
        $subMode->chunkById(100, function ($list) use ($finish) {
            foreach ($list as $item) {
                static $index = 1;
                $id = $item->article_id;
                $userId = $item->author_id;
                $originalUrl = $item->original_url;
                $article = SysArticle::getModel($id)->where('article_id', $id)->first();
                $ossUrl = service()->ConfigService->getConfigByKey('ossUrl');

                if (!empty($article)) {
                    // 获取缺省图片列表
                    $replace = LogReplaceResources::getModel()
                        ->where('target_id', $id)
                        ->where('target_type', GlobalConst::TARGET_TYPE_ARTICLE)
                        ->where('is_replaced', GlobalArticleConst::REPLACE_NO)
                        ->get()->toArray();

                    // 上传图片到oss并更新图片
                    foreach ($replace as $key => $rep) {
                        $type = $rep['type'];
                        $url = $rep['url'];
                        if (GlobalArticleConst::REPLACE_TYPE_COVER ==  $type) {
                            $newImg = $this->_uploadImgToOss($id, $url, GlobalUploadConst::OBJECT_ARTICLE_COVER_PATH);
                            if ($newImg) {
                                $this->info("[$index-$key/$finish] upload image to oss : " . $ossUrl . '/' . GlobalUploadConst::OBJECT_ARTICLE_EDITOR_PATH . $newImg);
                                $article->update(['cover_url' => $newImg]);
                            }
                        } elseif (GlobalArticleConst::REPLACE_TYPE_CONTENT ==  $type) {
                            $newImg = $this->_uploadImgToOss($id, $url, GlobalUploadConst::OBJECT_ARTICLE_EDITOR_PATH);
                            if ($newImg) {
                                $this->info("[$index-$key/$finish] upload image to oss : " . $ossUrl . '/' . GlobalUploadConst::OBJECT_ARTICLE_EDITOR_PATH . $newImg);
                                $content = str_replace($url, $ossUrl . '/' . GlobalUploadConst::OBJECT_ARTICLE_EDITOR_PATH . $newImg, $article->content);
                                // 保存替换记录
                                $this->saveRepair($id, $userId, $newImg, $url);
                                // 把旧内容，替换图片后的内容存入[spider_content]
                                $this->_saveSpiderContent($originalUrl, $article->content, $content);
                                // 修改[sys_article_content_]内容
                                $this->_saveArticleContent($id, $content);
                            }
                        }
                    }
                    // 重新查询一次文章信息
                    $article = SysArticle::getModel($id)->where('article_id', $id)->first();
                    //  文章内容同步到oss
                    $res = $this->_toOss($article);
                    // 更新记录为已替换
                    LogReplaceResources::getModel()->where('target_id', $id)->update(['is_replaced' => GlobalArticleConst::REPLACE_DONE]);
                    // log info
                    $res = $res ? 'SUCCESS' : 'FAILED';
                    $info = "[$index/$finish] upload to oss [article/info] [$res] [".env('APP_ENV')."] : "
                        .GlobalUploadConst::OBJECT_ARTICLE_INFO_PATH."$id.json .";
                    $this->logger($info, true, 'script');
                } else {
                    $info = "[$index/$finish] article not exist [sys_article] : $id .";
                    $this->warn($info);
                }
                $index++;
            }
        }, 'article_id');
        return 1;
    }

    /**
     * 文章修复 - 获取爬取原始内容，并同步到 Oss
     * [!] 伴随定时任务，每天凌晨两点十分执行，处理三天以内的文章
     * @return int
     */
    public function upload()
    {
        // 接收参数
        $isForce = $this->_getIsForce();
        $subMode = $this->_getSubModel();
        $finish = $subMode->count();
        // 每 100 单位合并处理一次
        $subMode->chunkById(100, function ($list) use ($finish, $isForce) {
            foreach ($list as $item) {
                static $index = 1;
                $id = $item->article_id;
                $userId = $item->author_id;
                $url = $item->original_url;
                // 原文链接为空，则跳过
                if (empty($url)) {
                    $info = "[$index/$finish] original_url is empty : $id - $url";
                    $this->warn($info);
                    $this->logger($info, false, 'script');
                    continue;
                }
                // 如果是全部执行，每 500 篇暂停 3 秒
                if ($index%500 == 0) sleep(3);
                $spider = SpiderInfo::getModel()->where('url', $url)->first();
                $article = SysArticle::getModel($id)->where('article_id', $id)->first();
                if (!empty($spider)) {
                    // 原文链接已失效，跳过
                    if ($spider->is_invalid == GlobalSpiderConst::INVALID_ENS) {
                        $info = "[$index/$finish] original_url is invalid : $id - $url";
                        $this->warn($info);
                        $this->logger($info, false, 'script');
                        continue;
                    }
                    // 0.1s 执行一次
                    usleep(100000);
                    // 获取爬取信息
                    $ossSpider = $this->_getOssSpider($article, $isForce);
                    $ossSpiderContent = $ossSpider['content'] ?? '';
                    // log info
                    $res = $ossSpiderContent ? 'SUCCESS' : 'FAILED';
                    $info = "[$index/$finish] upload oss [article/spider] [$res] -  $id .";
                    $this->logger($info, true, 'script');
                } else {
                    $info = "[$index/$finish] spider info not exist [spider_info] : $id - $url .";
                    $this->warn($info);
                    $this->logger($info, false, 'script');
                }
                $index++;
            }
        }, 'article_id');
        return 1;
    }

    /**
     * 文章修复 - 刷新文章内容
     * [!] 调老鹰接口 interhost:8083/article/refresh/ID ，刷新文章数据
     * @return int
     */
    public function refresh()
    {
        $subMode = $this->_getSubModel();
        $finish = $subMode->count();
        // 每 100 单位合并处理一次
        $subMode->chunkById(100, function ($list) use ($finish) {
            foreach ($list as $item) {
                static $index = 1;
                $id = $item->article_id;
                $article = SysArticle::getModel($id)->where('article_id', $id)->first();
                if (!empty($article)) {
                    // 刷新文章数据
                    $refreshUrl = getConfigByKey('articleContentRefreshUrl');
                    $res = HttpClient::getInstance()->get($refreshUrl . $id);
                    // log info
                    $res = json_encode($res);
                    $info = "[$index/$finish] refresh data [article/info] [$res] - $id .";
                    $this->logger($info, true, 'script');
                } else {
                    $info = "[$index/$finish] article not exist [sys_article] : $id .";
                    $this->warn($info);
                }
                $index++;
            }
        }, 'article_id');
        return 1;
    }

    /**
     * 文章修复 - 修复老原创文章数据
     * [!] 查询老库的原创文章ID，更新文章类型为原创，添加发布动态+关注列表动态
     * @return int
     */
    public function original()
    {
        $subMode = $this->_getSubModel();
        $finish = $subMode->count();
        // 每 100 单位合并处理一次
        $subMode->chunkById(500, function ($list) use ($finish) {
            foreach ($list as $item) {
                static $index = 1;
                $id = $item->article_id;
                $userId = $item->author_id;
                $article = SysArticle::getModel($id)->where('article_id', $id)->first();
                if (!empty($article->id)) {
                    // 验证作者身份
                    $identity = User::getModel()->where('user_id', $userId)->value('identity');
                    if ($identity != GlobalUserConst::IDENTITY_AUTHOR) {
                        $info = "[$index/$finish] user identity by [$identity] - uid - $userId : $id .";
                        $this->warn($info);
                        $index ++;
                        continue;
                    }
                    // 更新文章类型
                    if ($article->type != GlobalArticleConst::ARTICLE_TYPE_ORIGINAL) {
                        $article->update(['type' => GlobalArticleConst::ARTICLE_TYPE_ORIGINAL]);
                    }
                    // 添加文章动态
                    $queueMessageBody = [
                        'user_id' => $userId,
                        'type' => GlobalDynamicConst::DYNAMIC_TYPE_ORIGINAL_ARTICLE,
                        'target_id' => $id,
                        'target_type' => GlobalConst::TARGET_TYPE_ARTICLE,
                    ];
                    $res = service()->DynamicService->addDynamicByTarget($queueMessageBody);
                    // @todo 注意： 此处没有维护用户原创文章计数
                    // log info
                    $res = json_encode($res);
                    $info = "[$index/$finish] fix data [article/original] [$res] - $id .";
                    $this->logger($info, true, 'script');
                } else {
                    $info = "[$index/$finish] article not exist [sys_article] : $id .";
                    $this->warn($info);
                }
                $index++;
            }
        }, 'article_id');
        return 1;
    }

/*===========================================================以下为私有方法===========================================================*/

    /**
     * get ids
     * @return array
     */
    private function _getIds()
    {
        $ids = $this->option('ids');
        return $ids ? explode(',', $ids) : [];
    }

    /**
     * get last id
     * @return mixed
     */
    private function _getLastId()
    {
        $id = $this->option('lastId');
        return $id;
    }

    /**
     * get is force
     * @return bool
     */
    private function _getIsForce()
    {
        $isForce = $this->option('isForce');
        return !$isForce || 'false' == $isForce ? false : true;
    }

    /**
     * get cache days
     * @return bool
     */
    private function _getCacheDays()
    {
        $days = $this->option('cacheDays');
        return intval($days);
    }

    /**
     * get sub model
     * @param array $ids
     * @param integer $cacheDays
     * @param string $lastId
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|mixed
     */
    private function _getSubModel($ids = [], $lastId = '', $cacheDays= 0)
    {
        $ids = $ids ? : $this->_getIds();
        $lastId = $lastId ? : $this->_getLastId();
        $cacheDays = $cacheDays ? : $this->_getCacheDays();
        $subMode = SysArticle::getSubModel(function (Builder $query) use ($ids, $lastId, $cacheDays) {
            $query->where('is_deleted', GlobalConst::IS_NOT_DELETE);
            if(empty($ids) && empty($lastId) && empty($cacheDays)) {
                // 如果 ids 和 lastId 都不传，则不查出任何结果
                $query->whereIn('article_id', [0]);
            } else {
                if ($ids) $query->whereIn('article_id', $ids);
                if ($lastId) $query->where('article_id', '>', $lastId);
            }
            if ($cacheDays) $query->whereBetween('created_at', [time() - 3*86400, time()]);
        }, ['id', 'article_id', 'author_id', 'original_url', 'type', 'spider_original_id', 'created_at']);
        return $subMode;
    }

    /**
     * get replace ids
     * @return array|mixed
     */
    private function _getReplaceIds()
    {
        $ids = LogReplaceResources::getModel()
            ->where('target_type', GlobalConst::TARGET_TYPE_ARTICLE)
            ->where('is_replaced', GlobalArticleConst::REPLACE_NO)
            ->pluck('target_id');
        return $ids ? : [];
    }

    /**
     * upload img to oss
     * @param $id
     * @param $url
     * @param $path
     * @return string
     */
    private function _uploadImgToOss($id, $url, $path)
    {
        $newImg = Oss::getInstance(
            $path,
            GlobalUploadConst::TYPE_ARTICLE,
            GlobalUploadConst::FROM_FRONTEND,
            $id
        )->upload($url, 0, '', [], true);
        return $newImg ? : '';
    }

    /**
     * 获取文章内容中的外链图片列表
     * @param $content
     * @return array
     */
    private function _getImageList($content)
    {
        $pattern = "/<img((?!src).)*src[\s]*=[\s]*[\'\"](?<src>[^\'\"]*)[\'\"]/i";
        preg_match_all($pattern, $content, $out, PREG_PATTERN_ORDER);
        $srcAry = $out['src'];
        $srcAry = ArrayHelper::merge($srcAry, ['data:image/png']);
        $imgList = [];
        foreach ($srcAry as $src) {
            if (false === strpos($src, 'data:image') && false === strpos($src, 'tingjunapp')) {
                $imgList[] = $src;
            }
        }
        return $imgList;
    }

    /**
     * 获取不包含图片的文章内容，差异化对比用
     * @param $content
     * @return array
     */
    private function _getNoImageContent($content)
    {
        $pattern = "/<img((?!src).)*src[\s]*=[\s]*[\'\"](?<src>[^\'\"]*)[\'\"]/i";
        preg_match_all($pattern, $content, $out, PREG_PATTERN_ORDER);
        $srcAry = $out['src'];
        $srcAry = ArrayHelper::merge($srcAry, ['data:image/png']);
        $content = str_replace($srcAry, 'https://__IMG__', $content);
        return $content;
    }

    /**
     * 保存替换记录
     * @param $articleId
     * @param $authorId
     * @param $resourceFileName
     * @param $originalUrl
     * @return bool
     */
    private function saveRepair($articleId, $authorId, $resourceFileName, $originalUrl)
    {
        $articleRepairUrl = SysArticleRepairUrl::getModel($articleId)
            ->where([
                'article_id'   => $articleId,
                'original_url' => $originalUrl,
            ])->first();
        if ($articleRepairUrl) {
            return true;
        }
        SysArticleRepairUrl::getModel($articleId)->update([
            'user_id'            => $authorId,
            'article_id'         => $articleId,
            'original_url'       => $originalUrl,
            'resource_file_name' => $resourceFileName,
            'type'               => GlobalArticleConst::REPLACE_IMAGE_TYPE_CONTENT,
        ]);
        Console::getInstance(UploadCommand::class)
            ->push([
                'user_id'            => $authorId,
                'article_id'         => $articleId,
                'original_url'       => $originalUrl,
                'resource_file_name' => $resourceFileName,
                'type'               => GlobalArticleConst::REPLACE_IMAGE_TYPE_CONTENT,
                'status'             => GlobalUploadConst::UPLOAD_STATUS_SUCCESS
            ], GlobalConsoleConst::UPLOAD_LOG, 'sync');
        return true;
    }

    /**
     * 更新[spider_content]记录
     * @param string $url 链接地址
     * @param string $oldContent 没有替换图片地址的内容
     * @param string $newContent 替换图片地址后的内容
     * @return bool
     */
    private function _saveSpiderContent($url, $oldContent, $newContent)
    {
        $data = [
            'url'              => $url,
            'content'          => $newContent,
            'original_content' => $oldContent,
        ];
        $spiderContent = SpiderContent::getModel()->where(['url' => $url])->first();
        if (!$spiderContent) {
            $spiderContent = SpiderContent::getModel()->create($data);
        }
        return $spiderContent->update($data);
    }

    /**
     * 更新文章内容
     * @param $articleId
     * @param $content
     * @return bool
     */
    private function _saveArticleContent($articleId, $content)
    {
        $data = [
            'article_id' => $articleId,
            'content'    => $content,
        ];
        $articleContent = SysArticleContent::getModel($articleId)->where(['article_id' => $articleId])->first();
        if (!$articleContent) {
            return $articleContent = SysArticleContent::getModel($articleId)->create($data);
        }
        return $articleContent->update($data);
    }

    /***
     * 同步文章内容到 oss
     * @param SysArticle $article
     * @return int
     */
    private function _toOss($article)
    {
        // 将文章信息静态化到第三方
        return Oss::getInstance(GlobalUploadConst::OBJECT_ARTICLE_INFO_PATH)->uploadTarget([
            'id'          => $article->article_id,
            'title'       => $article->title,
            'cover_url'   => $article->cover_url,
            'description' => $article->description,
            'content'     => $article->content,
        ]);
    }

    /**
     * 获取文章原始爬取信息，若没有则重新抓取并上传到 oss
     * @param SysArticle $article
     * @param bool $isForce 是否强制重新爬取并更新
     * @return array|bool|mixed|string|void
     */
    private function _getOssSpider($article, $isForce = false)
    {
        $url = $article->original_url;
        $articleId = $article->article_id;
        if (!$isForce) {
            // 尝试从 oss 中获取数据
            $data = Oss::getInstance(GlobalUploadConst::OBJECT_ARTICLE_SPIDER_PATH)
                ->getObject($articleId. '.json');
            $data = Json::decode($data);
            if (!empty($data)) {
                return $data;
            }
        }
        // 重新抓取
        $res = service()->SpiderService->getSpiderScriptResult($url);
        if (empty($res)) {
            // 更新文章已失效
            SpiderInfo::getModel()->where('url', $url)->update(['is_invalid' => GlobalSpiderConst::INVALID_ENS]);
            $this->warn(" - [sync to oss [article/spider] failed] article is invalid - $articleId - $url");
            return [];
        }
        $res = ArrayHelper::merge(['id' => $article->article_id], $res);
        // 上传到 oss
        $upload = Oss::getInstance(GlobalUploadConst::OBJECT_ARTICLE_SPIDER_PATH)->uploadTarget($res);
        if (empty($upload)) {
            $this->warn(" - [sync to oss [article/spider] failed] upload error - $articleId - $url");
            return [];
        }
        $this->info(" - [sync to oss [article/spider] success] - $articleId - $url");
        return $res;
    }

}
